﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Server01
{
    class Program
    {
        static Socket udpServer;//Socket对象 就是用于进行网络通信的,它包括UDP协议和TCP协议.
                                //IP+端口 IP:对应的终端 端口:程序

        //使用list这样的数据结构,我们就可以对这个对象connections进行添加、删除、判断是否包含、遍历等等的一些操作.
        static List<IPEndPoint> connections = new List<IPEndPoint>();
        static string message;//缓存接收到的单个消息.
        static List<string> messageList = new List<string>();//管理所有接收到的消息


        static void Main(string[] args)
        {
            //第二个参数 表示数据报的形式 第三个参数 表示UDP协议.
            udpServer = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            udpServer.Bind(new IPEndPoint(IPAddress.Parse("172.17.0.5"), 7788));//IPAddress.Parse 将字符串转化为IP的形式 第二个参数是端口的意思
            ReceiveData();
            //Thread t = new Thread(ReceiveData);
            //t.IsBackground = true;
            //t.Start();
        }

        //在这个方法里,我们要不断的接收数据: 终端-接收 数据-缓存
        private static void ReceiveData()
        {

            while (true)
            {
                //接收数据的终端 IPAddress.Any这个参数表示任意终端的意思 0表示任意的端口
                EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
                //data是用于缓存接收的数据 --接收到的数据是字节数组,我们还需要转化为string类型,才能看得懂其意思
                byte[] data = new byte[1024];
                //ref 就可以将实际数据的IP和端口 写入到remoteEndPoint对象.
                //length表示接收到的数据的大小
                int length = udpServer.ReceiveFrom(data, ref remoteEndPoint);
                //将data字节数组,转化为string类型的数据  第一个参数是要转化的对象 第二个参数是数组起始的位置 最后一个是结束的位置.
                message = Encoding.UTF8.GetString(data, 0, length);

                if (length > 0)
                {
                    if (message != "quit")
                    {//如果不是quit退出指令
                        if (message == "login")
                        {//如果是登录指令,就将终端用户添加到管理的对象里,该对象表示所有已在线的用户.
                            //它要实现的功能,就是给这些在线的用户发送消息.

                            //判断connections是否已经包含了该IP和端口IPEndPoint 如果还未包含
                            if (!connections.Contains((remoteEndPoint as IPEndPoint)))
                            {
                                //将发送login消息的客户端的IP和端口,添加到connections里.
                                //我们把发送消息的终端添加到list里,主要是稍后从list里拿到它,向它也转发消息.
                                connections.Add(remoteEndPoint as IPEndPoint);
                                //向控制台打印消息:XXip:xx端口已上线
                                Console.WriteLine("ip:" + (remoteEndPoint as IPEndPoint).Address.ToString() + ":" + (remoteEndPoint as IPEndPoint).Port + "...已上线...");
                            }
                        }
                        else
                        {//如果不是登录指令,表示我们接收到了用户正常的聊天内容.
                            //我们就要把这些内容给发送所有在线的客户端.
                            messageList.Add(message);//管理所有已经接收到的数据.把这些数据发送给所有在线的客户端.
                                                     //往控制台打印一下,从哪个IP、端口接收到了什么消息--message
                            Console.WriteLine("从ip:" + (remoteEndPoint as IPEndPoint).Address.ToString() + ":" + (remoteEndPoint as IPEndPoint).Port + "...收到了数据:" + message);
                            SendData();

                        }
                    }
                    else
                    {//如果接收到的数据是quit退出的指令.

                        //connections如果包含了该客户端的IP和端口,就将它Remove移除掉.
                        if (connections.Contains((remoteEndPoint as IPEndPoint)))
                        {
                            //移除掉该客户端之后,其它消息就不会往该客户端发送了.
                            connections.Remove(remoteEndPoint as IPEndPoint);
                            //并且在控制台打印下线的IP
                            Console.WriteLine("ip:" + (remoteEndPoint as IPEndPoint).Address.ToString() + ":" +
                       (remoteEndPoint as IPEndPoint).Port + "已下线....");
                        }
                    }

                }
            }

        }


        //这是一个发送数据的方法,往各个在线的客户端发送.
        private static void SendData()
        {
            //数据缓存 所有在线的终端 调用发送的方法/socket实例

            //缓存所有数据的对象
            string[] s = new string[1024];

            //在线的客户端大于0 并且 接收到的数据 大于0
            if (connections.Count > 0 && messageList.Count > 0)
            {
                //向控制台打印下实时的客户端个数以及消息个数
                Console.WriteLine("客户端个数:" + connections.Count + "消息个数:" + messageList.Count);
                for (int i = 0; i < messageList.Count; i++)
                {
                    s[i] = messageList[i];
                    //就是要将发送的数据转化为字节数组,才可以进行发送.
                    byte[] data = Encoding.UTF8.GetBytes(s[i]);
                    foreach (var item in connections)
                    {//将数据发送给所有在线的用户/终端
                        //调用Socket实例的SendTo方法进行发送数据
                        udpServer.SendTo(data, item);//数据 发送的终端

                    }
                    //释放已经发送出去的数据,因为我们无需再对这个数据进行处理.
                    messageList.Remove(s[i]);
                    s[i] = null;
                }
            }
        }
    }
}

//总结:其实就是构造一个Socket实例,然后调用它的SendTo方法进行发送数据,调用ReceiveFrom方法进行接收数据 
//无论是接收数据还是发送数据,涉及到:1.数据(字节数组类型) 2.终端(IP和端口)

//因为我们这个控制台程序,是用于做服务端的,所有如果想要实现公网通信的话,就需要放到一个远程主机上,并且将Socket.Bind方法,所绑定的IP设置为公网的IP.
//如果是在本机电脑,就可以使用局域网/路由器提供的IP或者是 127.0.0.1本地IP进行通信.



